/**
  ******************************************************************************
  * @file    I2C_Config.c
  * @brief   STM32硬件I2C总线驱动实现
  * @author  18408107475@163.com
  * @version V1.0
  * @date    2025-7-15
  * @note    功能说明：
  *          - 基于STM32硬件I2C外设（I2C2）实现通信
  *          - 支持标准I2C协议（7位地址，100kHz速率）
  *          - 提供单字节/多字节读写接口，兼容大多数I2C设备（如MPU6050、OLED）
  * @硬件依赖：
  *          - 使用I2C2外设（挂载在APB1总线，时钟36MHz）
  *          - GPIO引脚配置为复用开漏模式（需外接4.7KΩ上拉电阻）
  *          - 支持的设备地址：7位地址（自动转换为8位含读写标志）
  ******************************************************************************
  */

#include "I2C_Config.h"

/**
  * @brief  等待I2C特定事件发生（私有辅助函数）
  * @param  I2Cx: 目标I2C外设（如I2C2）
  * @param  event: 待等待的事件（如I2C_EVENT_MASTER_MODE_SELECT）
  * @retval true: 事件发生，false: 超时（事件未发生）
  * @note   事件检测机制：
  *         1. 轮询I2C状态寄存器，检查是否发生目标事件
  *         2. 超时计数（I2C_TIMEOUT_MAX）防止死循环
  *         3. 超时后发送停止信号，恢复总线状态
  *         支持的关键事件：
  *         - I2C_EVENT_MASTER_MODE_SELECT：起始信号发送成功
  *         - I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED：从机应答写地址
  *         - I2C_EVENT_MASTER_BYTE_TRANSMITTED：字节发送完成
  *         - I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED：从机应答读地址
  *         - I2C_EVENT_MASTER_BYTE_RECEIVED：字节接收完成
  */
static bool I2C_WaitEvent(I2C_TypeDef* I2Cx, uint32_t event)
{
    uint32_t timeout = I2C_TIMEOUT_MAX;  // 超时计数器（约65535次循环）
    while (!(I2C_CheckEvent(I2Cx, event)))  // 检查事件是否发生
    {
        if (--timeout == 0)  // 超时判断
        {
            I2C_GenerateSTOP(I2Cx, ENABLE);  // 发送停止信号，释放总线
            return false;  // 返回超时错误
        }
    }
    return true;  // 事件发生，返回成功
}

/**
  * @brief  初始化I2C2外设及对应GPIO
  * @note   配置流程：
  *         1. 使能GPIO和I2C时钟
  *         2. 配置GPIO为复用开漏模式（I2C总线必需）
  *         3. 配置I2C2参数（速率、地址模式、应答等）
  *         4. 使能I2C2外设
  * @硬件配置细节：
  *         - GPIO模式：AF_OD（复用开漏）→ 支持I2C总线的线与逻辑
  *         - 时钟速度：100000Hz（标准模式，兼容大多数设备）
  *         - 地址模式：7位地址（主流I2C设备采用）
  *         - 应答使能：I2C_Ack_Enable → 接收数据时发送ACK
  */
void I2C_Config(void)
{
    // 使能GPIO时钟和AFIO时钟（复用功能需要AFIO支持）
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    // 使能I2C2时钟（I2C2挂载在APB1总线）
    RCC_APB1PeriphClockCmd(I2C_CLK, ENABLE);
    
    // 配置I2C对应的GPIO引脚（SCL和SDA）
    GPIO_InitTypeDef GPIO_InitStructure = {
        .GPIO_Mode  = GPIO_Mode_AF_OD,  			// 复用开漏模式：输出时为开漏，输入时复用为I2C
        .GPIO_Pin   = I2C_SCL_PIN | I2C_SDA_PIN,  	// SCL和SDA引脚（如PB6和PB7）
        .GPIO_Speed = GPIO_Speed_50MHz,  			// 引脚速度50MHz（I2C无需高速，但需匹配总线速率）
    };
    GPIO_Init(I2C_PORT, &GPIO_InitStructure);       // 初始化GPIO

    // 配置I2C2参数
    I2C_InitTypeDef I2C_InitStructure = {
        .I2C_Ack                  = I2C_Ack_Enable,  				// 使能应答（接收时发送ACK）
        .I2C_AcknowledgedAddress  = I2C_AcknowledgedAddress_7bit,  	// 7位地址模式
        .I2C_ClockSpeed           = 100000,  						// 时钟速度100kHz（标准模式）
        .I2C_DutyCycle            = I2C_DutyCycle_2,  				// 时钟占空比2/1
        .I2C_Mode                 = I2C_Mode_I2C,  					// I2C模式（而非SMBus模式）
        .I2C_OwnAddress1          = 0x00,  							// 主机自身地址（作为从机时使用，此处无用）
    };
    I2C_Init(I2C2, &I2C_InitStructure);  // 初始化I2C2

    I2C_Cmd(I2C2, ENABLE);  // 使能I2C2外设
}

/**
  * @brief  向I2C设备的指定寄存器写入单字节数据
  * @param  dev_addr: 设备7位I2C地址（如0x68）
  * @param  reg_addr: 目标寄存器地址（如MPU6050的ACCEL_XOUT_H）
  * @param  data: 要写入的数据字节
  * @retval true: 写入成功, false: 写入失败（含超时）
  * @I2C通信时序：
  *         1. 主机发送START信号
  *         2. 主机发送"设备地址+写标志"（dev_addr<<1 | 0）
  *         3. 等待从机应答（ACK）
  *         4. 主机发送寄存器地址（reg_addr）
  *         5. 等待从机应答
  *         6. 主机发送数据（data）
  *         7. 等待从机应答
  *         8. 主机发送STOP信号
  */
bool I2C_Write_Data(u8 dev_addr, u8 reg_addr, u8 data)
{
    while(I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY));
    
    /* 发送起始信号 */
    I2C_GenerateSTART(I2C2, ENABLE);
    // 等待"起始信号发送成功"事件（主机进入主机模式）
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT))
        return false;

    /* 发送设备地址+写标志（7位地址左移1位+0） */
    I2C_Send7bitAddress(I2C2, (dev_addr << 1), I2C_Direction_Transmitter);
    // 等待"从机应答写地址"事件（主机进入发送模式）
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
        return false;

    /* 发送寄存器地址 */
    I2C_SendData(I2C2, reg_addr);
    // 等待"寄存器地址发送完成"事件
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING))
        return false;

    /* 发送数据 */
    I2C_SendData(I2C2, data);
    // 等待"数据发送完成"事件
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
        return false;

    /* 发送停止信号，释放总线 */
    I2C_GenerateSTOP(I2C2, ENABLE);
    return true;
}

/**
  * @brief  从I2C设备的指定寄存器读取单字节数据
  * @param  dev_addr: 设备7位I2C地址（如0x3C）
  * @param  reg_addr: 目标寄存器地址（如OLED的0x00命令寄存器）
  * @retval 读取到的数据（失败时返回0）
  * @I2C通信时序：
  *         1. 主机发送START信号
  *         2. 主机发送"设备地址+写标志"（dev_addr<<1 | 0）
  *         3. 等待从机应答
  *         4. 主机发送寄存器地址（reg_addr）
  *         5. 等待从机应答
  *         6. 主机发送RESTART信号（重复起始）
  *         7. 主机发送"设备地址+读标志"（dev_addr<<1 | 1）
  *         8. 等待从机应答
  *         9. 主机接收数据（关闭ACK，因为只接收1字节）
  *         10. 主机发送STOP信号
  */
u8 I2C_Read_Data(u8 dev_addr, u8 reg_addr)
{
    u8 data = 0;
    
    // 等待总线空闲
    while(I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY));
    
    /* 1. 发送起始条件 */
    I2C_GenerateSTART(I2C2, ENABLE);
    if(!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT))
        return 0;
    
    /* 2. 发送设备地址(写) */
    I2C_Send7bitAddress(I2C2, dev_addr << 1, I2C_Direction_Transmitter);
    if(!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
        return 0;
    
    /* 3. 发送寄存器地址 */
    I2C_SendData(I2C2, reg_addr);
    if(!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
        return 0;
    
    /* 4. 发送重复起始条件 */
    I2C_GenerateSTART(I2C2, ENABLE);
    if(!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT))
        return 0;
    
    /* 5. 发送设备地址(读) */
    I2C_Send7bitAddress(I2C2, dev_addr << 1, I2C_Direction_Receiver);
    if(!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
        return 0;
    
    /* 6. 禁用ACK(因为只读取一个字节) */
    I2C_AcknowledgeConfig(I2C2, DISABLE);
    
    /* 7. 发送停止条件(提前发送，实际在接收完成后生效) */
    I2C_GenerateSTOP(I2C2, ENABLE);
    
    /* 8. 等待数据接收完成 */
    if(!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED))
        return 0;
        
    data = I2C_ReceiveData(I2C2);
    
    /* 9. 恢复ACK使能 */
    I2C_AcknowledgeConfig(I2C2, ENABLE);
    
    return data;
}

/**
  * @brief  向I2C设备的指定寄存器写入多字节数据
  * @param  dev_addr: 设备7位I2C地址
  * @param  reg_addr: 目标寄存器地址
  * @param  len: 数据长度（字节数）
  * @param  data: 要写入的数据数组指针（需保证非NULL）
  * @retval true: 写入成功, false: 写入失败
  * @I2C通信时序：
  *         前半部分同单字节写入（START→设备地址→寄存器地址）
  *         后半部分循环发送len个数据字节，最后发送STOP
  *         注：每个字节发送后都需等待从机应答
  */
bool I2C_Write_MultiData(u8 dev_addr, u8 reg_addr, u8 len, u8* data)
{
    if (data == NULL || len == 0)  // 参数校验（防止空指针或零长度）
        return false;

    /* 发送起始信号 */
    I2C_GenerateSTART(I2C2, ENABLE);
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT))
        return false;

    /* 发送设备地址+写标志 */
    I2C_Send7bitAddress(I2C2, (dev_addr << 1), I2C_Direction_Transmitter);
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
        return false;

    /* 发送寄存器地址 */
    I2C_SendData(I2C2, reg_addr);
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
        return false;

    /* 循环发送多字节数据 */
    for (u8 i = 0; i < len; i++)
    {
        I2C_SendData(I2C2, data[i]);  // 发送第i个字节
        if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
            return false;  // 任一字节发送失败则整体失败
    }

    /* 发送停止信号 */
    I2C_GenerateSTOP(I2C2, ENABLE);
    return true;
}

/**
  * @brief  从I2C设备的指定寄存器读取多字节数据
  * @param  dev_addr: 设备7位I2C地址
  * @param  reg_addr: 目标寄存器地址
  * @param  len: 要读取的数据长度（字节数）
  * @param  data: 存储读取数据的数组指针（需保证非NULL且足够大）
  * @retval true: 读取成功, false: 读取失败
  * @I2C通信时序：
  *         前半部分同单字节读取（START→写地址→寄存器地址→RESTART→读地址）
  *         后半部分循环接收len个字节：
  *         - 前len-1个字节：接收后发送ACK
  *         - 第len个字节：接收前关闭ACK，接收后发送STOP
  */
bool I2C_Read_MultiData(u8 dev_addr, u8 reg_addr, u8 len, u8* data)
{
    if (data == NULL || len == 0)  // 参数校验
        return false;

    /* 发送起始信号，设置寄存器地址（同单字节读取） */
    I2C_GenerateSTART(I2C2, ENABLE);
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT))
        return false;

    /* 发送设备地址+写标志 */
    I2C_Send7bitAddress(I2C2, (dev_addr << 1), I2C_Direction_Transmitter);
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
        return false;

    /* 发送寄存器地址 */
    I2C_SendData(I2C2, reg_addr);
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
        return false;

    /* 重复起始信号，切换到读模式 */
    I2C_GenerateSTART(I2C2, ENABLE);
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT))
        return false;

    /* 发送读地址 */
    I2C_Send7bitAddress(I2C2, (dev_addr << 1), I2C_Direction_Receiver);
    if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
        return false;

    /* 循环接收多字节数据 */
    for (u8 i = 0; i < len; i++)
    {
        if (i == len - 1)  // 最后一个字节
        {
            /* 关闭ACK（告诉从机这是最后一个字节） */
            I2C_AcknowledgeConfig(I2C2, DISABLE);
        }

        // 等待接收完成
        if (!I2C_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED))
        {
            I2C_GenerateSTOP(I2C2, ENABLE);  // 出错时释放总线
            I2C_AcknowledgeConfig(I2C2, ENABLE);  // 恢复ACK
            return false;
        }

        data[i] = I2C_ReceiveData(I2C2);  // 读取数据
    }

    /* 发送停止信号 */
    I2C_GenerateSTOP(I2C2, ENABLE);
    /* 恢复ACK使能 */
    I2C_AcknowledgeConfig(I2C2, ENABLE);
    
    return true;
}
